/*
Authors: Ram Ovadia 201374022 and Lior Fass 300069705
Project: HW Assignment 4 
Description: This is the implementation of the server module used in the project.  
*/

#include "Server.h"

// Signatures of functions
Server_attr *server_init(char * IpAddress,char * graphTxt,int maxClients,char* ServerLogFilename);
void server_cleanup(Server_attr *server_attr);
BOOL Create_GPS_Process(Server_attr *serverInst);
int Run_Listener(Server_attr *serverInst);
DWORD ServiceThread(Service_thread_attr *service_thread_input);
static int FindFirstUnusedThreadSlot(Server_attr *serverInst);
static void CleanupWorkerThreads(Server_attr * serverInst);
int socket_bind_listen(Server_attr *serverInst);
int getQuitFun();

/*
 * Main function of server, initialize the server, initialize GPS, Runs Listener and calls Cleanup in the end
 *
 * Input:
 * -----
 * IpAddress - string that holds the IP, after validity check and localhost interpret 
 * graphTxt - string that holds path for the graph file
 * maxClients - integer of maximum clients to support, after validity check
 * ServerLogFilename - string contains the log filename for output logging
 * 
 * Output:
 * -----
 * 0 - successful.
 * 1 - failed. 
 */
int server_main(char* IpAddress,char* graphTxt,int maxClients, char* ServerLogFilename)
{
	Server_attr* serverInst = NULL;

	//initialize server
	serverInst = server_init(IpAddress, graphTxt, maxClients, ServerLogFilename);
	if (serverInst == NULL)
	{
		printf("failed creating server instance\n");
		server_cleanup(serverInst);
		return 1;
	}

	//initialize GPS Process
	if (!Create_GPS_Process(serverInst))
	{
		printf("failed creating GPS PROCESS\n");
		server_cleanup(serverInst);
		return 1;
	}

	// Run Listener
	if (!Run_Listener(serverInst))
	{
		server_cleanup(serverInst);
		return 1;
	}
	else
	{
		server_cleanup(serverInst);
		return 0;
	}
	
}

/*
 * initialize server struct
 *
 * Input:
 * -----
 * same ad server_main
 *
 * Output:
 * -----
 * Server_attr struct with all fields initialized
 * NULL - if failed. 
 */
Server_attr *server_init(char * IpAddress,char * graphTxt,int maxClients,char* ServerLogFilename)
{
	FILE *graphFile = NULL;
	Server_attr *server_attr = (Server_attr*)calloc(1,sizeof(Server_attr));
	if (server_attr == NULL)
	{
		printf("failed to allocate server attributes\n");
		server_cleanup(server_attr);
		return NULL;
	}

	// Save ip address as dotted decimal to attributes struct
	server_attr->server_IP_addr = (char*)malloc(sizeof(char)*(strlen(IpAddress)+1));
	if (server_attr->server_IP_addr == NULL)
	{
		printf("failed to allocate server attributes server IP\n");
		server_cleanup(server_attr);
		return NULL;
	}
	strcpy(server_attr->server_IP_addr,IpAddress);

	//read Graph from file
	server_attr->graph = LoadMapFromFile(graphTxt);
	if ( server_attr->graph == NULL)
	{
		printf("reading graph failed\n");
		server_cleanup(server_attr);
		return NULL;
	}

	server_attr->maxClients = maxClients;

	//Open log file
	server_attr->log_file = fopen(ServerLogFilename,"w");
	if (server_attr->log_file == NULL)
	{
		printf("failed opening log file\n");
		server_cleanup(server_attr);
		return NULL;
	}

	server_attr->serverListenSocket = INVALID_SOCKET;

	// Initialize all thread handles to NULL, to mark that they have not been initialized
	server_attr->ThreadHandles = (HANDLE *)calloc(maxClients,sizeof(HANDLE));
	if (server_attr->ThreadHandles == NULL)
	{
		printf("failed allocating array of thread handles\n");
		server_cleanup(server_attr);
		return NULL;
	}

	server_attr->ThreadSockets = (SOCKET*)calloc(maxClients,sizeof(SOCKET));
	if (server_attr->ThreadSockets == NULL)
	{
		printf("failed allocating array of thread sockets\n");
		server_cleanup(server_attr);
		return NULL;
	}

	server_attr->graphMutex = CreateMutex(NULL, FALSE, NULL);
	if (server_attr->graphMutex == NULL)
	{
		printf("failed creating graph mutex\n");
		server_cleanup(server_attr);
		return NULL;
	}

	server_attr->gps_semaphore = NULL;

	//init succeed
	return server_attr;
}

/*
 * cleans the server structs and releases all alocated memory, releases socket env
 *
 * Input:
 * -----
 * server_attr - server_attr struct.
 *
 */
void server_cleanup(Server_attr *server_attr)
{
	//Clean all attributes
	if (server_attr != NULL)
	{
		if (server_attr->gps_semaphore != NULL)
		{
			ReleaseSemaphore(server_attr->gps_semaphore,1,NULL);
		}

		CleanupWorkerThreads(server_attr);
		
		if (server_attr->graph != NULL)
		{
			map_print(server_attr->graph,server_attr->log_file);
			map_clean(server_attr->graph);
		}

		if (server_attr->graphMutex != NULL)
			CloseHandle(server_attr->graphMutex);

		if (server_attr->log_file != NULL)
			fclose(server_attr->log_file);

		if (server_attr->serverListenSocket != INVALID_SOCKET)
			closesocket(server_attr->serverListenSocket);

		if (server_attr->server_IP_addr != NULL)
			free(server_attr->server_IP_addr);

		free(server_attr);
		server_attr = NULL;
	}
	WSACleanup();
}

/*
 * Creates the GPS Process and set the Semaphore for signaling to it.
 *
 * Input:
 * -----
 * server_attr - server_attr struct, using the GPS semaphors handle
 *
 * Output:
 * -----
 * TRUE - GPS process Run successful.
 * FALSE - process creation failed. 
 */
BOOL Create_GPS_Process(Server_attr *serverInst)
{
	PROCESS_INFORMATION process_info;
	SECURITY_ATTRIBUTES sec = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE};
	char gps_sem_name[] = GPS_SEMAPHORE_STR;
	char *gps_cmd;

	gps_cmd = (char*)malloc(36 + strlen(serverInst->server_IP_addr));
	if (gps_cmd == NULL)
	{
		printf("failed to allocate gps cmd\n");
		return FALSE;
	}

	sprintf(gps_cmd,"GPS.exe %s %d",serverInst->server_IP_addr,serverInst->maxClients);

	process_info.dwProcessId = 0;
	process_info.dwThreadId = 0;
	process_info.hProcess = NULL;
	process_info.hThread = NULL;

	serverInst->gps_semaphore = CreateSemaphore(&sec, 0, 1, gps_sem_name);
	if (serverInst->gps_semaphore == NULL)
	{
		printf("failed to create gps semaphrpore\n");
		free(gps_cmd);
		return FALSE;
	}

	if (!CreateProcessSimple(gps_cmd, &process_info))
	{
		printf("failed to create process of command: %s with error code %d\n", gps_cmd, GetLastError());
		free(gps_cmd);
		return FALSE;
	}
	
	free(gps_cmd);
	return TRUE;
}

/*
 * runs the listener socket on the main thread, untill user types "quit"
 *
 * Input:
 * -----
 * serverInst - serverInst struct. 
 *
 * Output:
 * -----
 * 0 - successful. Listener finished successful 
 * 1 - failed. 
 */
int Run_Listener(Server_attr *serverInst)
{
	int Ind;
	int WaitRes;
	FD_SET ReadSet;
	struct timeval SelectTimeout;
	SOCKET AcceptSocket = INVALID_SOCKET;
	WSADATA wsaData;
	int WSAStartupRes;
	int quitThreadRes;
	HANDLE hQuitThread = NULL;

	Service_thread_attr *service_thread_input = (Service_thread_attr*)malloc(sizeof(Service_thread_attr)*serverInst->maxClients);
	if (service_thread_input == NULL)
	{
		printf("failed allocating array of service thread input attributes\n");
		return 1;
	}

	// Initialize Winsock.
	WSAStartupRes = WSAStartup( MAKEWORD( 2, 2 ), &wsaData );	           

	if ( WSAStartupRes != NO_ERROR )
	{
		printf( "error %ld at WSAStartup( ), ending program.\n", WSAGetLastError() );
		free(service_thread_input);
		return 1;
	}

	if (socket_bind_listen(serverInst) != 0)
	{
		printf( "error %ld at Socket/Bind/Listen, ending program.\n", WSAGetLastError() );
		free(service_thread_input);
		return 1;
	}

	//run "quit" checking thread
	hQuitThread = CreateThread(NULL,0,( LPTHREAD_START_ROUTINE ) getQuitFun,NULL,0,	NULL);

	while(TRUE)
	{
		SelectTimeout.tv_sec = 1;
		SelectTimeout.tv_usec = 0;
		FD_ZERO(&ReadSet);
		FD_SET(serverInst->serverListenSocket, &ReadSet);
		WaitRes = select(0, &ReadSet, NULL, NULL, &SelectTimeout);
		if ( WaitRes == SOCKET_ERROR || WaitRes == 0)
		{
			quitThreadRes = WaitForSingleObject(hQuitThread,0);
			if (quitThreadRes == WAIT_OBJECT_0)
			{
				CloseHandle(hQuitThread);
				break;
			}
			else
				continue; // No socket was ready.
		}
		AcceptSocket = accept( serverInst->serverListenSocket, NULL, NULL );
		if ( AcceptSocket == INVALID_SOCKET )
		{
			printf( "Accepting connection with client failed, error %ld\n", WSAGetLastError() ) ; 
			continue;
		}

		Ind = FindFirstUnusedThreadSlot(serverInst);

		if ( Ind == serverInst->maxClients ) //no slot is available
		{ 
			closesocket( AcceptSocket ); //Closing the socket, dropping the connection.
			continue;
		} 
		else 	
		{
			writeLogAndScreen(serverInst->log_file,"Received connection from client%d\n",Ind+1);
			serverInst->ThreadSockets[Ind] = AcceptSocket;
			service_thread_input[Ind].Ind = Ind;
			service_thread_input[Ind].server_attr = serverInst;
			serverInst->ThreadHandles[Ind] = CreateThread(
				NULL,
				0,
				( LPTHREAD_START_ROUTINE ) ServiceThread,
				&( service_thread_input[Ind] ),
				0,
				NULL
				);
		}
	} 

	free(service_thread_input);
	service_thread_input = NULL;
	return 0;
}

/*
 * service provider for each successful connection of client
 *
 * Input:
 * -----
 * service_thread_input - struct containing two elements, the thread index, and the server_struct for using all locks and shared resources
 *
 * Output:
 * -----
 * 0 - connection closed after successfully serving client. 
 * 1 - connection closed after failure in serving client.
 */
DWORD ServiceThread( Service_thread_attr *service_thread_input ) 
{
	BOOL Done = FALSE;
	TransferResult_t SendRes;
	TransferResult_t RecvRes;
	int Ind = service_thread_input->Ind;
	char* seralizedMap;
	Server_attr *serverInst = service_thread_input->server_attr;
	SOCKET tSocket = serverInst->ThreadSockets[Ind];

	// Send client the GRAPH
	DWORD wait_result = WaitForSingleObject((serverInst->graphMutex), INFINITE);
	if ( (wait_result == WAIT_FAILED) || (wait_result == WAIT_ABANDONED) )
	{
		printf("Failed locking graph before send\n");
		closesocket( tSocket );
		return 1;
	}
	seralizedMap = map_marshal(serverInst->graph);
	if (seralizedMap == NULL)
	{
		printf("failed Marshaling map\n");
		closesocket( tSocket );
		return 1;
	}
	ReleaseMutex(serverInst->graphMutex);
	SendRes = SendString( seralizedMap, tSocket );
	free(seralizedMap);
	if ( SendRes == TRNS_FAILED ) 
	{
		printf( "Service socket error while writing, closing thread.\n" );
		closesocket( tSocket );
		return 1;
	}
	
	writeLogAndScreen(serverInst->log_file,"Sent the road map to client%d\n",Ind+1);

	// on recieving update, lock graph and update it.
	while ( !Done ) 
	{		
		char *recievedStr = NULL;
		DWORD wait_result;

		RecvRes = ReceiveString( &recievedStr , tSocket );

		if ( RecvRes == TRNS_FAILED )
		{
			printf( "Service socket error while reading from client %d\n",Ind);
			closesocket( tSocket );
			return 1;
		}
		else if ( RecvRes == TRNS_DISCONNECTED )
		{
			break;
		}
		else
		{
			//Update MAP
			wait_result = WaitForSingleObject((serverInst->graphMutex), INFINITE);
			if ( (wait_result == WAIT_FAILED) || (wait_result == WAIT_ABANDONED) )
			{
				printf("Failed locking graph before send to client %d\n",Ind);
				closesocket( tSocket );
				return 1;
			}
			UpdateMap(recievedStr,serverInst->graph,serverInst->log_file,Ind);
			ReleaseMutex(serverInst->graphMutex);
		}

		free( recievedStr );		
	}

	writeLogAndScreen(serverInst->log_file,"Closed connection from client%d\n",Ind+1);
	closesocket( tSocket );
	return 0;
}

/*
 * find the index of the available thread, if no free thread return ind = max client
 *
 * Input:
 * -----
 * serverInst structs containing the shared resources
 *
 * Output:
 * -----
 * ind < Max_client if there is free thread 
 * Max_client if all threads are occupied 
 */
int FindFirstUnusedThreadSlot(Server_attr *serverInst)
{ 
	int Ind;

	for ( Ind = 0; Ind < serverInst->maxClients; Ind++ )
	{
		if ( serverInst->ThreadHandles[Ind] == NULL )
			break;
		else
		{
			// poll to check if thread finished running:
			DWORD Res = WaitForSingleObject( serverInst->ThreadHandles[Ind], 0 ); 

			if ( Res == WAIT_OBJECT_0 ) // this thread finished running
			{				
				CloseHandle( serverInst->ThreadHandles[Ind] );
				serverInst->ThreadHandles[Ind] = NULL;
				break;
			}
		}
	}

	return Ind;
}

/*
 * Clean Up all workers, close sockets and handles after they finished serving clients
 *
 * Input:
 * -----
 * serverInst - struct containing shared resources(the threads handles and sockets)
 *
 */
static void CleanupWorkerThreads(Server_attr *serverInst)
{
	int Ind; 

	for ( Ind = 0; Ind < serverInst->maxClients ; Ind++ )
	{
		if ( serverInst->ThreadHandles[Ind] != NULL )
		{
			// poll to check if thread finished running:
			DWORD Res = WaitForSingleObject( serverInst->ThreadHandles[Ind], INFINITE ); 

			if ( Res == WAIT_OBJECT_0 ) 
			{
				closesocket(  serverInst->ThreadSockets[Ind] );
				CloseHandle( serverInst->ThreadHandles[Ind] );
				serverInst->ThreadHandles[Ind] = NULL;
				break;
			}
			else
			{
				printf( "Waiting for thread failed. Ending program\n" );
				return;
			}
		}
	}
}

/*
 * function that initialize server connection using socket bind and listen function.
 *
 * Input:
 * -----
 * serverInst - shared resources struct
 *
 * Output:
 * -----
 * 0 - successful. listener successfuly established. 
 * 1 - failed. go to cleanup 
 */
int socket_bind_listen(Server_attr *serverInst)
{
	unsigned long Address;
	SOCKADDR_IN service;
	int bindRes;
	int ListenRes;
	// Create a socket.    
	serverInst->serverListenSocket = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
	if ( serverInst->serverListenSocket == INVALID_SOCKET ) 
	{
		printf( "Error at socket( ): %ld\n", WSAGetLastError( ) );
		return 1;
	}

	// Bind the socket.
	Address = inet_addr( serverInst->server_IP_addr );
	if ( Address == INADDR_NONE || Address == INADDR_ANY)
	{
		printf("The string \"%s\" cannot be converted into an ip address. ending program.\n",serverInst->server_IP_addr  );
		return 1;
	} 
	service.sin_family = AF_INET;
	service.sin_addr.s_addr = Address;
	service.sin_port = htons( SERVER_PORT ); 
	bindRes = bind( serverInst->serverListenSocket, ( SOCKADDR* ) &service, sizeof( service ) );
	if ( bindRes == SOCKET_ERROR ) 
	{
		printf( "bind( ) failed with error %ld. Ending program\n", WSAGetLastError( ) );
		return 1;
	}

	// Listen on the Socket.
	ListenRes = listen( serverInst->serverListenSocket, SOMAXCONN );
	if ( ListenRes == SOCKET_ERROR ) 
	{
		printf( "Failed listening on socket, error %ld.\n", WSAGetLastError() );
		return 1;
	}

	//Socket ready
	return 0;
}

/*
 * small function that runs in seperate thread in a loop and listens to stdin untill user types "quit"
 * the listener checks this functions thread and close the program when it returned
 *
 * Output:
 * -----
 * 0 - successful. user typed quit in the cmd line 
 */
int getQuitFun()
{
	char buffer[MAX_LINE_LENGTH];

	while(TRUE)
	{
		gets(buffer);
		if(!strcmp(buffer,"quit"))
			break;
	}
	return 0;
}

